﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace JOATServer.SocietyFramework.Core.XML
{
    public partial class XMLAutomaton : BaseXMLAutomaton
    {
        /// <summary>
        /// Class used to serialize the current Automaton.
        /// </summary>
        protected class XMLAutomatonSerializer
        {
            protected XMLAutomaton Owner { get; set; }
            protected StreamWriter outputStream;

            public XMLAutomatonSerializer(XMLAutomaton owner)
            {
                this.Owner = owner;
            }

            /// <summary>
            /// Begins the serialization of the current Automaton to the given OutputStream.
            /// </summary>
            /// <param name="os">stream where the automaton serialization is written.</param>
            /// <returns>true if operation succeeded, false otherwise</returns>
            public bool Serialize(Stream os)
            {
                outputStream = new StreamWriter(os);
                outputStream.Write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");					//header of the XML
                outputStream.Write("<{0} {1}=\"{2}\" {3}=\"{4}\" {5}=\"{6}\">", 
                    new object[]{ AUTOMATON_TAG,                                                    //automaton declaration
                    NAME_ATTRIBUTE, Owner.Name, 
                    CURRENT_STATE_ATTRIBUTE, Owner.currentStateName,
                    REGISTER_AT_START_ATTRIBUTE, 
					MessageRouter.Instance.SubscriberNames.Contains(Owner.Name) ? ".T." : ".F."});
                SerializeCurrentMessage();
                SerializeMessageList();
                SerializeVariables();
                SerializeStates();
                outputStream.Write("</{0}>", AUTOMATON_TAG);
                outputStream.Flush();
                return true;
            }

            protected void SerializeCurrentMessage()
            {
                if (Owner.currentMessage != null)
                {
                    outputStream.Write("<{0} {1}=\"{2}\" {3}=\"{4}\" />",
                        CURRENT_MESSAGE_TAG,
                        FROM_ATTRIBUTE, Owner.currentMessage.From != null ? Owner.currentMessage.From : "",
                        DATA_ATTRIBUTE, Owner.currentMessage.Data != null ? Owner.currentMessage.Data : "");
                }
            }

            protected void SerializeMessageList()
            {
                outputStream.Write("<{0}>", MESSAGE_LIST_TAG);
			    Message message;
			    while((message=Owner.messageList.Remove(1))!=null){
				    outputStream.Write("<{0} {1}=\"{2}\" {3}=\"{4}\" />", MESSAGE_TAG,
						    FROM_ATTRIBUTE, message.From,
						    DATA_ATTRIBUTE, message.Data);
			    }
			    outputStream.Write("</{0}>", MESSAGE_LIST_TAG);
            }

            protected void SerializeVariables()
            {
                outputStream.Write("<{0}>", VARIABLES_TAG);
                foreach (KeyValuePair<string, object> entry in Owner.variables)
                {
                    SerializeVariable(entry.Key, entry.Value);
                }
                outputStream.Write("</{0}>", VARIABLES_TAG);
            }

            protected void SerializeVariable(string name, object value)
            {
                if (value is Dictionary<object, object>)
                {					//when it's a HashMap write the variables in it recursively
                    foreach (KeyValuePair<object, object> entry in (Dictionary<object, object>)value)
                    {
                        string keyString = name;
                        object keyObject = entry.Key;

                        if (keyObject is string)
                        {		//key string construction
                            keyString += "['" + keyObject.ToString() + "']";
                        }
                        else if (keyObject is Boolean)
                        {
                            if ((Boolean)keyObject)
                            {
                                keyString += "[.T.]";
                            }
                            else
                            {
                                keyString += "[.F.]";
                            }
                        }
                        else
                        {
                            keyString += "[" + keyObject.ToString() + "]";
                        }

                        SerializeVariable(keyString, entry.Value);
                    }

                }
                else
                {												//when it's not a HashMap write it normally
                    string valueString = null;

                    if (value is string)
                    {
                        valueString = "'" + value.ToString() + "'";
                    }
                    else if (value is Boolean)
                    {
                        if ((Boolean)value)
                        {
                            valueString = ".T.";
                        }
                        else
                        {
                            valueString = ".F.";
                        }
                    }
                    else
                    {
                        valueString = value.ToString();
                    }

                    outputStream.Write("<{0} {1}=\"{2}\" {3}=\"{4}\" />",
                            VARIABLE_TAG, NAME_ATTRIBUTE, name, VALUE_ATTRIBUTE, valueString);
                }
            }

            protected void SerializeStates()
            {
                outputStream.Write("<{0}>", STATES_TAG);
                foreach (KeyValuePair<string, State> entry in Owner.states)
                {
                    SerializeState(entry.Key, (XMLState)entry.Value);
                }
                outputStream.Write("</{0}>", STATES_TAG);
            }

            protected void SerializeState(string name, XMLState state)
            {
                outputStream.Write("<{0} {1}=\"{2}\">", STATE_TAG, NAME_ATTRIBUTE, name);

                outputStream.Write("<{0}>", CODE_TAG);					//serialize the code inside the state
                if (state.instruction is InstructionBlock)
                {
                    SerializeInstructionBlock((InstructionBlock)state.instruction);
                }
                else
                {
                    SerializeInstruction(state.instruction);
                }
                outputStream.Write("</{0}>", CODE_TAG);

                if (state.transition is XMLTransitionGroup)
                {		//serialize the transition / transition group
                    SerializeTransitionGroup(null, (XMLTransitionGroup)state.transition);
                }
                else
                {
                    SerializeTransition(null, (XMLTransition)state.transition);
                }

                outputStream.Write("</{0}>", STATE_TAG);
            }

            protected void SerializeTransitionGroup(string name, XMLTransitionGroup transitionGroup)
            {
                if (name != null)
                {
                    outputStream.Write("<{0} {1}=\"{2}\">", TRANSITION_GROUP_TAG, NAME_ATTRIBUTE, name);
                }
                else
                {
                    outputStream.Write("<{0}>", TRANSITION_GROUP_TAG);
                }

                outputStream.Write("<{0}>", CODE_TAG);					//serialize the code inside the state
                if (transitionGroup.instruction is InstructionBlock)
                {
                    SerializeInstructionBlock((InstructionBlock)transitionGroup.instruction);
                }
                else
                {
                    SerializeInstruction(transitionGroup.instruction);
                }
                outputStream.Write("</{0}>", CODE_TAG);

                foreach (KeyValuePair<string, Transition> entry in transitionGroup.transitions)
                {
                    if (entry.Value is XMLTransitionGroup)
                    {
                        SerializeTransitionGroup(entry.Key, (XMLTransitionGroup)entry.Value);
                    }
                    else
                    {
                        SerializeTransition(entry.Key, (XMLTransition)entry.Value);
                    }
                }

                outputStream.Write("</{0}>", TRANSITION_GROUP_TAG);
            }

            protected void SerializeTransition(string name, XMLTransition transition)
            {
                if (name != null)
                {
                    outputStream.Write("<{0} {1}=\"{2}\">", TRANSITION_TAG, NAME_ATTRIBUTE, name);
                }
                else
                {
                    outputStream.Write("<{0}>", TRANSITION_TAG);
                }

                if (transition.instruction is InstructionBlock)
                {
                    SerializeInstructionBlock((InstructionBlock)transition.instruction);
                }
                else
                {
                    SerializeInstruction(transition.instruction);
                }

                outputStream.Write("</{0}>", TRANSITION_TAG);
            }

            protected void SerializeInstructionBlock(InstructionBlock instructionBlock)
            {
                foreach (Instruction instruction in instructionBlock.Instructions)
                {
                    SerializeInstruction(instruction);
                }
            }

            protected void SerializeInstruction(Instruction instruction)
            {
                if (instruction is WhileInstruction)
                {
                    SerializeWhile((WhileInstruction)instruction);
                }
                else if (instruction is DoWhileInstruction)
                {
                    SerializeDoWhile((DoWhileInstruction)instruction);
                }
                else if (instruction is ForInstruction)
                {
                    SerializeFor((ForInstruction)instruction);
                }
                else if (instruction is IfElseInstruction)
                {
                    SerializeIfElse((IfElseInstruction)instruction);
                }
                else if (instruction is SwitchInstruction)
                {
                    SerializeSwitch((SwitchInstruction)instruction);
                }
                else if (instruction is ExpressionContainerInstruction)
                {
                    SerializeExpressionContainer((ExpressionContainerInstruction)instruction);
                }
                else if (instruction is GetNextMessageInstruction)
                {
                    SerializeGetNextMessage((GetNextMessageInstruction)instruction);
                }
                else if (instruction is SendMessageInstruction)
                {
                    SerializeSendMessage((SendMessageInstruction)instruction);
                }
                else
                {	//else it's a returning instruction
                    SerializeReturn(instruction);
                }
            }

            protected void SerializeWhile(WhileInstruction instruction)
            {
                string testExpression = DeconstructExpression(instruction.testInstruction);
                outputStream.Write("<{0} {1}=\"{2}\">", WHILE_TAG, TEST_ATTRIBUTE, testExpression);

                if (instruction.repeatInstruction is InstructionBlock)
                {
                    SerializeInstructionBlock((InstructionBlock)instruction.repeatInstruction);
                }
                else
                {
                    SerializeInstruction(instruction.repeatInstruction);
                }

                outputStream.Write("</{0}>", WHILE_TAG);
            }

            protected void SerializeDoWhile(DoWhileInstruction instruction)
            {
                string testExpression = DeconstructExpression(instruction.testInstruction);
                outputStream.Write("<{0} {1}=\"{2}\">", DO_WHILE_TAG, TEST_ATTRIBUTE, testExpression);

                if (instruction.repeatInstruction is InstructionBlock)
                {
                    SerializeInstructionBlock((InstructionBlock)instruction.repeatInstruction);
                }
                else
                {
                    SerializeInstruction(instruction.repeatInstruction);
                }

                outputStream.Write("</{0}>", DO_WHILE_TAG);
            }

            protected void SerializeFor(ForInstruction instruction)
            {
                string assignExpression = DeconstructExpression(instruction.assignInstruction);
                string testExpression = DeconstructExpression(instruction.testInstruction);
                string stepExpression = DeconstructExpression(instruction.stepInstruction);
                outputStream.Write("<{0} {1}=\"{2}\" {3}=\"{4}\" {5}=\"{6}\">", FOR_TAG,
                        ASSIGN_ATTRIBUTE, assignExpression, TEST_ATTRIBUTE, testExpression, STEP_ATTRIBUTE, stepExpression);

                if (instruction.repeatInstruction is InstructionBlock)
                {
                    SerializeInstructionBlock((InstructionBlock)instruction.repeatInstruction);
                }
                else
                {
                    SerializeInstruction(instruction.repeatInstruction);
                }

                outputStream.Write("</{0}>", FOR_TAG);
            }

            protected void SerializeIfElse(IfElseInstruction instruction)
            {
                string testExpression = DeconstructExpression(instruction.testInstruction);
                outputStream.Write("<{0} {1}=\"{2}\">", IF_TAG, TEST_ATTRIBUTE, testExpression);

                if (instruction.succeedInstruction is InstructionBlock)
                {			//succeed instruction
                    SerializeInstructionBlock((InstructionBlock)instruction.succeedInstruction);
                }
                else
                {
                    SerializeInstruction(instruction.succeedInstruction);
                }

                outputStream.Write("</{0}>", IF_TAG);

                if (instruction.failInstruction != null)
                {
                    outputStream.Write("<{0}>", ELSE_TAG);

                    if (instruction.failInstruction is InstructionBlock)
                    {			//fail instruction
                        SerializeInstructionBlock((InstructionBlock)instruction.failInstruction);
                    }
                    else
                    {
                        SerializeInstruction(instruction.failInstruction);
                    }

                    outputStream.Write("</{0}>", ELSE_TAG);
                }
            }

            protected void SerializeSwitch(SwitchInstruction instruction)
            {
                string valueExpression = DeconstructExpression(instruction.argumentInstruction);

                outputStream.Write("<{0} {1}=\"{2}\">", SWITCH_TAG, VALUE_ATTRIBUTE, valueExpression);

                foreach (CaseInstruction caseInstruction in instruction.caseInstructionsList)
                {	//cases
                    valueExpression = DeconstructExpression(caseInstruction.label);
                    outputStream.Write("<{0} {1}=\"{2}\">", CASE_TAG, VALUE_ATTRIBUTE, valueExpression);

                    if (caseInstruction.instruction is InstructionBlock)
                    {
                        SerializeInstructionBlock((InstructionBlock)caseInstruction.instruction);
                    }
                    else
                    {
                        SerializeInstruction(caseInstruction.instruction);
                    }

                    outputStream.Write("</{0}>", CASE_TAG);
                }

                if (instruction.defaultInstruction != null)
                {		//default
                    outputStream.Write("<{0}>", DEFAULT_TAG);

                    Instruction defaultInstruction = ((CaseInstruction)instruction.defaultInstruction).instruction;
                    if (defaultInstruction is InstructionBlock)
                    {
                        SerializeInstructionBlock((InstructionBlock)defaultInstruction);
                    }
                    else
                    {
                        SerializeInstruction(defaultInstruction);
                    }

                    outputStream.Write("</{0}>", DEFAULT_TAG);
                }

                outputStream.Write("</{0}>", SWITCH_TAG);
            }

            protected void SerializeExpressionContainer(ExpressionContainerInstruction instruction)
            {
                string expressionExpression = DeconstructExpression(instruction.expression);
                outputStream.Write("<{0} {1}=\"{2}\" />", EXECUTE_TAG, EXPRESSION_ATTRIBUTE, expressionExpression);
            }

            protected void SerializeGetNextMessage(GetNextMessageInstruction instruction)
            {
                outputStream.Write("<{0} />", GET_NEXT_MESSAGE_TAG);
            }

            protected void SerializeSendMessage(SendMessageInstruction instruction)
            {
                string toExpression = DeconstructExpression(instruction.to);
                string dataExpression = DeconstructExpression(instruction.data);
                outputStream.Write("<{0} {1}=\"{2}\" {3}=\"{4}\" />", SEND_MESSAGE_TAG,
                        TO_ATTRIBUTE, toExpression, DATA_ATTRIBUTE, dataExpression);
            }

            protected void SerializeReturn(Instruction instruction)
            {
                if (instruction is ValueInstruction)
                {
                    object value = ((ValueInstruction)instruction).value;
                    if (value == null)
                    {
                        outputStream.Write("<{0} />", RETURN_TAG);
                        return;
                    }
                    else if (value.Equals(SpecialSymbols.BREAK))
                    {
                        outputStream.Write("<{0} />", BREAK_TAG);
                        return;
                    }
                    else if (value.Equals(SpecialSymbols.CONTINUE))
                    {
                        outputStream.Write("<{0} />", CONTINUE_TAG);
                        return;
                    }
                }
                string valueExpression = DeconstructExpression(instruction);
                outputStream.Write("<{0} {1}=\"{2}\" />", RETURN_TAG, VALUE_ATTRIBUTE, valueExpression);
            }


            /// <summary>
            /// Transforms an Instruction object into it's string equivalent.
            /// </summary>
            /// <param name="instruction">the Instruction to be transformed</param>
            /// <returns>string representing the Instruction</returns>
            protected string DeconstructExpression(Instruction instruction)
            {
                StringBuilder sb = new StringBuilder();

                if (instruction is ValueInstruction)
                {										//a value instruction
                    ValueInstruction vi = (ValueInstruction)instruction;
                    if (vi.value is string)
                    {						//string has ''
                        sb.Append("'");
                        sb.Append(vi.value);
                        sb.Append("'");
                    }
                    else
                    {											//the others don't
                        sb.Append(vi.value);
                    }

                }
                else if (instruction is VariableWithIndexInstruction)
                {
                    VariableWithIndexInstruction vwii = (VariableWithIndexInstruction)instruction;	//a variable with index
                    sb.Append(vwii.name);
                    foreach (Instruction index in vwii.indexValues)
                    {
                        sb.Append("[");
                        sb.Append(DeconstructExpression(index));
                        sb.Append("]");
                    }

                }
                else if (instruction is VariableInstruction)
                {								//a simple variable
                    sb.Append(((VariableInstruction)instruction).name);

                }
                else if (instruction is OneOperandInstruction)
                {				                //ONE OPERAND
                    if (instruction is NotInstruction)
                    {							//not instruction
                        sb.Append("!");
                        sb.Append(DeconstructExpression(((NotInstruction)instruction).operand));
                    }

                }
                else if (instruction is TwoOperandsInstruction)
                {				        //TWO OPERANDS
                    sb.Append("(");
                    sb.Append(DeconstructExpression(((TwoOperandsInstruction)instruction).leftOperand));

                    if (instruction is AssignInstruction)
                    {					//assign instruction
                        sb.Append("=");
                    }
                    else if (instruction is AdditionInstruction)
                    {					//addition instruction
                        sb.Append("+");
                    }
                    else if (instruction is SubtractionInstruction)
                    {				    //subtraction instruction
                        sb.Append("-");
                    }
                    else if (instruction is MultiplicationInstruction)
                    {			        //multiplication instruction
                        sb.Append("*");
                    }
                    else if (instruction is DivisionInstruction)
                    {					//division instruction
                        sb.Append("/");
                    }
                    else if (instruction is ModuloInstruction)
                    {					//modulo instruction
                        sb.Append("%");
                    }
                    else if (instruction is ModuloInstruction)
                    {					//modulo instruction
                        sb.Append("%");
                    }
                    else if (instruction is LessInstruction)
                    {					//less instruction
                        sb.Append("&lt;");
                    }
                    else if (instruction is LessOrEqualInstruction)
                    {				//less or equal instruction
                        sb.Append("&lt;=");
                    }
                    else if (instruction is GreaterInstruction)
                    {					//greater instruction
                        sb.Append("&gt;");
                    }
                    else if (instruction is GreaterOrEqualInstruction)
                    {			        //greater or equal instruction
                        sb.Append("&gt;=");
                    }
                    else if (instruction is EqualsInstruction)
                    {					//equals instruction
                        sb.Append("==");
                    }
                    else if (instruction is NotEqualsInstruction)
                    {					//not equals instruction
                        sb.Append("!=");
                    }
                    else if (instruction is AndInstruction)
                    {					//and instruction
                        sb.Append("&amp;&amp;");
                    }
                    else if (instruction is OrInstruction)
                    {					//or instruction
                        sb.Append("||");
                    }

                    sb.Append(DeconstructExpression(((TwoOperandsInstruction)instruction).rightOperand));
                    sb.Append(")");

                }
                else if (instruction is FunctionInstruction)
                {					//function instructions
                    if (instruction is GetCurrentMessageFromInstruction)
                    {
                        sb.Append(GET_CURRENT_MESSAGE_FROM_FUNCTION);
                    }
                    else if (instruction is GetCurrentMessageDataInstruction)
                    {
                        sb.Append(GET_CURRENT_MESSAGE_DATA_FUNCTION);
                    }
                    else if (instruction is IsCurrentMessageNullInstruction)
                    {
                        sb.Append(IS_CURRENT_MESSAGE_NULL_FUNCTION);
                    }
                    else if (instruction is ExecuteTransitionInstruction)
                    {
                        sb.Append(EXECUTE_TRANSITION_FUNCTION);
                    }
                    else if (instruction is SizeInstruction)
                    {
                        sb.Append(SIZE_FUNCTION);
                    }
                    else if (instruction is SplitInstruction)
                    {
                        sb.Append(SPLIT_FUNCTION);
                    }
                    else if (instruction is ParseIntegerInstruction)
                    {
                        sb.Append(PARSE_INTEGER_FUNCTION);
                    }

                    sb.Append("(");
                    if (((FunctionInstruction)instruction).parameters != null)
                    {
                        foreach (Instruction parameter in ((FunctionInstruction)instruction).parameters)
                        {   //parameter serialization
                            sb.Append(DeconstructExpression(parameter));
                            sb.Append(",");
                        }
                    }
                    if (sb[sb.Length - 1] == ',')
                    {
                        sb.Remove(sb.Length - 1, 1);
                    }
                    sb.Append(")");
                }

                return sb.ToString();
            }
        }
    }
}
